﻿using System.Collections.Generic;
using System.Linq;

namespace RapidDict.Modules.PronunciationService.Service
{
    using System;
    using System.IO;
    using System.Net;
    using System.Windows.Media;
    using System.Xml;
    using Infrastructure.Interfaces;
    using Infrastructure.Models;

    public class PronunciationService : IPronunciationService
    {
        private const string ForvoApiKey = "4a40dab374da8b2db954ff0c8fa8668f";
        private const int MaximumCacheNumber = 500;

        private static Dictionary<SoundPath, string> sourcePathCacheStorage = new Dictionary<SoundPath, string>();
        
        public bool IsPronounceable(string word, Language sourceLanguage)
        {
            string soundPath = GetSoundPath(word, sourceLanguage);
            if (soundPath != String.Empty)
            {
                return true;
            }
            
            return false;
        }

        public void Pronounce(string word, Language sourceLanguage)
        {
            var soundPath = GetSoundPath(word, sourceLanguage);
            if (soundPath != String.Empty)
            {
                var player = new MediaPlayer();
                player.Open(new Uri(soundPath));
                player.Play();
            }
        }

        private static string GetSoundPath(string word, Language sourceLanguage)
        {
            var cachedSoundPath = GetCachedSoundPath(word, sourceLanguage);
            if (cachedSoundPath != null)
            {
                return cachedSoundPath;
            }

            try
            {
                string soundPath = GetSoundPathFromForvoService(word, sourceLanguage);

                if (IsStorageOverflowed())
                {
                    ClearPathCacheStorage();   
                }
                
                AddToPathCacheStorage(word, sourceLanguage, soundPath);

                return soundPath;
            }
            catch
            {
                return string.Empty;
            }
        }

        private static void AddToPathCacheStorage(string word, Language sourceLanguage, string soundPath)
        {
            sourcePathCacheStorage.Add(new SoundPath(word, sourceLanguage), soundPath);
        }

        private static string GetSoundPathFromForvoService(string word, Language sourceLanguage)
        {
            var req = WebRequest.Create(GetForvoUrl(word, sourceLanguage));
            var response = req.GetResponse();
            var stream = response.GetResponseStream();

            var xmldoc = new XmlDocument();
            xmldoc.Load(stream);
            return xmldoc.SelectSingleNode("items/item/pathmp3").InnerText;
        }

        private static bool IsStorageOverflowed()
        {
            return sourcePathCacheStorage.Count() > MaximumCacheNumber;
        }

        private static void ClearPathCacheStorage()
        {
            sourcePathCacheStorage = new Dictionary<SoundPath, string>();
        }

        private static string GetCachedSoundPath(string word, Language sourceLanguage)
        {
            return (from pair in sourcePathCacheStorage
                    where pair.Key.Word == word && pair.Key.SourceLanguage == sourceLanguage
                    select pair.Value).FirstOrDefault();
        }

        private static string GetForvoUrl(string word, Language sourceLanguage)
        {
            string result = Path.Combine(@"http://apifree.forvo.com/action/word-pronunciations/format/xml/word", word);
            result = Path.Combine(result, @"language");
            result = Path.Combine(result, Language.GetTwoDigitCodeBy(sourceLanguage.Code));
            result = Path.Combine(result, @"order/rate-desc/limit/1/key");
            result = Path.Combine(result, ForvoApiKey);

            return result;
        }
    }

    internal class SoundPath
    {
        public SoundPath(string word, Language sourceLanguage)
        {
            this.Word = word;
            this.SourceLanguage = sourceLanguage;
        }

        public string Word { get; set; }

        public Language SourceLanguage { get; set; }
    }
}
